home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / lightning-0.8-tb-win.xpi / chrome / calendar.jar / content / calendar / migration.js < prev    next >
Text File  |  2008-01-08  |  31KB  |  746 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Calendar migration code
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  *   Joey Minta <jminta@gmail.com>
  18.  * Portions created by the Initial Developer are Copyright (C) 2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Matthew Willis <mattwillis@gmail.com>
  23.  *   Clint Talbert <cmtalbert@myfastmail.com>
  24.  *   Stefan Sitter <ssitter@gmail.com>
  25.  *
  26.  * Alternatively, the contents of this file may be used under the terms of
  27.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  28.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29.  * in which case the provisions of the GPL or the LGPL are applicable instead
  30.  * of those above. If you wish to allow use of your version of this file only
  31.  * under the terms of either the GPL or the LGPL, and not to allow others to
  32.  * use your version of this file under the terms of the MPL, indicate your
  33.  * decision by deleting the provisions above and replace them with the notice
  34.  * and other provisions required by the GPL or the LGPL. If you do not delete
  35.  * the provisions above, a recipient may use your version of this file under
  36.  * the terms of any one of the MPL, the GPL or the LGPL.
  37.  *
  38.  * ***** END LICENSE BLOCK ***** */
  39.  
  40. const SUNBIRD_UID = "{718e30fb-e89b-41dd-9da7-e25a45638b28}";
  41. const FIREFOX_UID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
  42.  
  43. //
  44. // The front-end wizard bits.
  45. //
  46. var gMigrateWizard = {
  47.     /**
  48.      * Called from onload of the migrator window.  Takes all of the migrators
  49.      * that were passed in via window.arguments and adds them to checklist. The
  50.      * user can then check these off to migrate the data from those sources.
  51.      */
  52.     loadMigrators: function gmw_load() {
  53.         var listbox = document.getElementById("datasource-list");
  54.  
  55.         //XXX Once we have branding for lightning, this hack can go away
  56.         var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  57.                   .getService(Components.interfaces.nsIStringBundleService);
  58.  
  59.         var props = sbs.createBundle("chrome://calendar/locale/migration.properties");
  60.  
  61.         if (gDataMigrator.isLightning()) {
  62.             var wizard = document.getElementById("migration-wizard");
  63.             var desc = document.getElementById("wizard-desc");
  64.             // Since we don't translate "Lightning"...
  65.             wizard.title = props.formatStringFromName("migrationTitle",
  66.                                                       ["Lightning"],
  67.                                                       1);
  68.             desc.textContent = props.formatStringFromName("migrationDescription",
  69.                                                           ["Lightning"],
  70.                                                           1);
  71.         }
  72.  
  73.         LOG("migrators: " + window.arguments.length);
  74.         for each (var migrator in window.arguments[0]) {
  75.             var listItem = document.createElement("listitem");
  76.             listItem.setAttribute("type", "checkbox");
  77.             listItem.setAttribute("checked", true);
  78.             listItem.setAttribute("label", migrator.title);
  79.             listItem.migrator = migrator;
  80.             listbox.appendChild(listItem);
  81.         }
  82.     },
  83.  
  84.     /**
  85.      * Called from the second page of the wizard.  Finds all of the migrators
  86.      * that were checked and begins migrating their data.  Also controls the
  87.      * progress dialog so the user can see what is happening. (somewhat)
  88.      */
  89.     migrateChecked: function gmw_migrate() {
  90.         var migrators = [];
  91.  
  92.         // Get all the checked migrators into an array
  93.         var listbox = document.getElementById("datasource-list");
  94.         for (var i = listbox.childNodes.length-1; i >= 0; i--) {
  95.             LOG("Checking child node: " + listbox.childNodes[i]);
  96.             if (listbox.childNodes[i].getAttribute("checked")) {
  97.                 LOG("Adding migrator");
  98.                 migrators.push(listbox.childNodes[i].migrator);
  99.             }
  100.         }
  101.  
  102.         // If no migrators were checked, then we're done
  103.         if (migrators.length == 0) {
  104.             window.close();
  105.         }
  106.  
  107.         // Don't let the user get away while we're migrating
  108.         //XXX may want to wire this into the 'cancel' function once that's
  109.         //    written
  110.         var wizard = document.getElementById("migration-wizard");
  111.         wizard.canAdvance = false;
  112.         wizard.canRewind = false;
  113.  
  114.         // We're going to need this for the progress meter's description
  115.         var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  116.                   .getService(Components.interfaces.nsIStringBundleService);
  117.         var props = sbs.createBundle("chrome://calendar/locale/migration.properties");
  118.         var label = document.getElementById("progress-label");
  119.         var meter = document.getElementById("migrate-progressmeter");
  120.  
  121.         var i = 0;
  122.         // Because some of our migrators involve async code, we need this
  123.         // call-back function so we know when to start the next migrator.
  124.         function getNextMigrator() {
  125.             if (migrators[i]) {
  126.                 var mig = migrators[i];
  127.  
  128.                 // Increment i to point to the next migrator
  129.                 i++;
  130.                 LOG("starting migrator: " + mig.title);
  131.                 label.value = props.formatStringFromName("migratingApp",
  132.                                                          [mig.title],
  133.                                                          1);
  134.                 meter.value = (i-1)/migrators.length*100;
  135.                 mig.args.push(getNextMigrator);
  136.  
  137.                 try {
  138.                     mig.migrate.apply(mig, mig.args);
  139.                 } catch (e) {
  140.                     LOG("Failed to migrate: " + mig.title);
  141.                     LOG(e);
  142.                     getNextMigrator();
  143.                 }
  144.             } else {
  145.                 LOG("migration done");
  146.                 wizard.canAdvance = true;
  147.                 label.value = props.GetStringFromName("finished");
  148.                 meter.value = 100;
  149.                 gMigrateWizard.setCanRewindFalse();
  150.             }
  151.          }
  152.  
  153.         // And get the first migrator
  154.         getNextMigrator();
  155.    },
  156.  
  157.     setCanRewindFalse: function gmw_finish() {
  158.         document.getElementById('migration-wizard').canRewind = false;
  159.     }
  160. };
  161.  
  162. //
  163. // The more back-end data detection bits
  164. //
  165. function dataMigrator(aTitle, aMigrateFunction, aArguments) {
  166.     this.title = aTitle;
  167.     this.migrate = aMigrateFunction;
  168.     this.args = aArguments;
  169. }
  170.  
  171. var gDataMigrator = {
  172.     mIsLightning: null,
  173.     mIsInFirefox: false,
  174.     mPlatform: null,
  175.     mDirService: null,
  176.     mIoService: null,
  177.  
  178.     /**
  179.      * Properly caches the service so that it doesn't load on startup
  180.      */
  181.     get dirService() {
  182.         if (!this.mDirService) {
  183.             this.mDirService = Components.classes["@mozilla.org/file/directory_service;1"]
  184.                                .getService(Components.interfaces.nsIProperties);
  185.         }
  186.         return this.mDirService;
  187.     },
  188.  
  189.     get ioService() {
  190.         if (!this.mIoService) {
  191.             this.mIoService = Components.classes["@mozilla.org/network/io-service;1"]
  192.                               .getService(Components.interfaces.nsIIOService);
  193.         }
  194.         return this.mIoService;
  195.     },
  196.  
  197.     /**
  198.      * Gets the value for mIsLightning, and sets it if this.mIsLightning is
  199.      * not initialized. This is used by objects outside gDataMigrator to
  200.      * access the mIsLightning member.
  201.      */
  202.     isLightning: function is_ltn() {
  203.         if (this.mIsLightning == null) {
  204.             var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
  205.                           .getService(Components.interfaces.nsIXULAppInfo);
  206.             this.mIsLightning = !(appInfo.ID == SUNBIRD_UID);
  207.             return this.mIsLightning;
  208.         }
  209.         // else mIsLightning is initialized, return the value
  210.         return this.mIsLightning;
  211.     },
  212.  
  213.     /**
  214.      * Call to do a general data migration (for a clean profile)  Will run
  215.      * through all of the known migrator-checkers.  These checkers will return
  216.      * an array of valid dataMigrator objects, for each kind of data they find.
  217.      * If there is at least one valid migrator, we'll pop open the migration
  218.      * wizard, otherwise, we'll return silently.
  219.      */
  220.     checkAndMigrate: function gdm_migrate() {
  221.         var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
  222.                       .getService(Components.interfaces.nsIXULAppInfo);
  223.         this.mIsLightning = !(appInfo.ID == SUNBIRD_UID)
  224.         LOG("mIsLightning is: " + this.mIsLightning);
  225.         if (appInfo.ID == FIREFOX_UID) {
  226.             this.mIsInFirefox = true;
  227.             // We can't handle Firefox Lightning yet
  228.             LOG("Holy cow, you're Firefox-Lightning! sorry, can't help.");
  229.             return;
  230.         }
  231.  
  232.         var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
  233.                          .getService(Components.interfaces.nsIXULRuntime);
  234.         this.mPlatform = xulRuntime.OS.toLowerCase();
  235.  
  236.         LOG("mPlatform is: " + this.mPlatform);
  237.  
  238.         var DMs = [];
  239.         var migrators = [this.checkOldCal, this.checkEvolution,
  240.                          this.checkIcal];
  241.         // XXX also define a category and an interface here for pluggability
  242.         for each (var migrator in migrators) {
  243.             var migs = migrator.call(this);
  244.             for each (var dm in migs) {
  245.                 DMs.push(dm);
  246.             }
  247.         }
  248.  
  249.         if (DMs.length == 0) {
  250.             // No migration available
  251.             return;
  252.         }
  253.         LOG("DMs: " + DMs.length);
  254.  
  255.         var url = "chrome://calendar/content/migration.xul";
  256. //@line 266 "/cygdrive/c/builds/tinderbox/Lt-Mozilla1.8/WINNT_5.2_Depend/mozilla/calendar/lightning/../base/content/migration.js"
  257.         openDialog(url, "migration", "modal,centerscreen,chrome,resizable=no", DMs);
  258. //@line 268 "/cygdrive/c/builds/tinderbox/Lt-Mozilla1.8/WINNT_5.2_Depend/mozilla/calendar/lightning/../base/content/migration.js"
  259.     },
  260.  
  261.     /**
  262.      * Checks to see if we can find any traces of an older moz-cal program.
  263.      * This could be either the old calendar-extension, or Sunbird 0.2.  If so,
  264.      * it offers to move that data into our new storage format.  Also, if we're
  265.      * if we're Lightning, it will disable the old calendar extension, since it
  266.      * conflicts with us.
  267.      */
  268.     checkOldCal: function gdm_calold() {
  269.         LOG("Checking for the old calendar extension/app");
  270.  
  271.         // First things first.  If we are Lightning and the calendar extension
  272.         // is installed, we have to nuke it.  The old extension defines some of
  273.         // the same paths as we do, and the resulting file conflicts result in
  274.         // first-class badness. getCompositeCalendar is a conflicting function
  275.         // that exists in Lighnting's version of calUtils.js.  If it isn't
  276.         // defined, we have a conflict.
  277.         if (this.isLightning() && !("getCompositeCalendar" in window)) {
  278.  
  279.             // We can't use our normal helper-functions, because those might
  280.             // conflict too.
  281.             var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  282.                       .getService(Components.interfaces.nsIStringBundleService);
  283.             var props = sbs.createBundle("chrome://calendar/locale/migration.properties");
  284.             var brand = sbs.createBundle("chrome://branding/locale/brand.properties");
  285.             var appName = brand.GetStringFromName("brandShortName");
  286.             // Tell the user we're going to disable and restart
  287.             var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  288.                                 .getService(Components.interfaces.nsIPromptService);
  289.             promptService.alert(window,
  290.                                 props.GetStringFromName("disableExtTitle"),
  291.                                 props.formatStringFromName("disableExtText",
  292.                                                            [brand],1));
  293.  
  294.             // Kiiillllll...
  295.             var em = Components.classes["@mozilla.org/extensions/manager;1"]
  296.                      .getService(Components.interfaces.nsIExtensionManager);
  297.             em.disableItem("{8e117890-a33f-424b-a2ea-deb272731365}");
  298.             promptService.alert(window, getString("disableDoneTitle"),
  299.                                 getString("disableExtDone"));
  300.             var startup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
  301.                           .getService(Components.interfaces.nsIAppStartup);
  302.             startup.quit(Components.interfaces.nsIAppStartup.eRestart |
  303.                          Components.interfaces.nsIAppStartup.eAttemptQuit);
  304.         }
  305.  
  306.         // This is the function that the migration wizard will call to actually
  307.         // migrate the data.  It's defined here because we may use it multiple
  308.         // times (with different aProfileDirs), for instance if there is both
  309.         // a Thunderbird and Firefox cal-extension
  310.         function extMigrator(aProfileDir, aCallback) {
  311.             // Get the old datasource
  312.             var dataSource = aProfileDir.clone();
  313.             dataSource.append("CalendarManager.rdf");
  314.             if (!dataSource.exists()) {
  315.                 return;
  316.             }
  317.  
  318.             // Let this be a lesson to anyone designing APIs. The RDF API is so
  319.             // impossibly confusing that it's actually simpler/cleaner/shorter
  320.             // to simply parse as XML and use the better DOM APIs.
  321.             var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
  322.                       .createInstance(Components.interfaces.nsIXMLHttpRequest);
  323.             req.open('GET', "file://" + dataSource.path, true);
  324.             req.onreadystatechange = function calext_onreadychange() {
  325.                 if (req.readyState == 4) {
  326.                     LOG(req.responseText);
  327.                     parseAndMigrate(req.responseXML, aCallback)
  328.                 }
  329.             };
  330.             req.send(null);
  331.         }
  332.  
  333.         // Callback from the XHR above.  Parses CalendarManager.rdf and imports
  334.         // the data describe therein.
  335.         function parseAndMigrate(aDoc, aCallback) {
  336.             // For duplicate detection
  337.             var calManager = getCalendarManager();
  338.             var uris = [];
  339.             for each (var oldCal in calManager.getCalendars({})) {
  340.                 uris.push(oldCal.uri);
  341.             }
  342.  
  343.             function getRDFAttr(aNode, aAttr) {
  344.                 return aNode.getAttributeNS("http://home.netscape.com/NC-rdf#",
  345.                                             aAttr);
  346.             }
  347.  
  348.             const RDFNS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
  349.             var nodes = aDoc.getElementsByTagNameNS(RDFNS, "Description");
  350.             LOG("nodes: " + nodes.length);
  351.             for (var i = 0; i < nodes.length; i++) {
  352.                 LOG("Beginning cal node");
  353.                 var cal;
  354.                 var node = nodes[i];
  355.                 if (getRDFAttr(node, "remote") == "false") {
  356.                     LOG("not remote");
  357.                     var localFile = Components.classes["@mozilla.org/file/local;1"]
  358.                                     .createInstance(Components.interfaces.nsILocalFile);
  359.                     localFile.initWithPath(getRDFAttr(node, "path"));
  360.                     cal = gDataMigrator.importICSToStorage(localFile);
  361.                 } else {
  362.                     // Remote subscription
  363.                     // XXX check for duplicates
  364.                     var url = makeURL(getRDFAttr(node, "remotePath"));
  365.                     cal = calManager.createCalendar("ics", url);
  366.                 }
  367.                 cal.name = getRDFAttr(node, "name");
  368.                 cal.setProperty("color", getRDFAttr(node, "color"));
  369.                 getCompositeCalendar().addCalendar(cal);
  370.             }
  371.             aCallback();
  372.         }
  373.  
  374.         var migrators = [];
  375.  
  376.         // Look in our current profile directory, in case we're upgrading in
  377.         // place
  378.         var profileDir = this.dirService.get("ProfD", Components.interfaces.nsILocalFile);
  379.         profileDir.append("Calendar");
  380.         if (profileDir.exists()) {
  381.             LOG("Found old extension directory in current app");
  382.             var title;
  383.             if (this.mIsLightning) {
  384.                 title = "Mozilla Calendar Extension";
  385.             } else {
  386.                 title = "Sunbird 0.2";
  387.             }
  388.             migrators.push(new dataMigrator(title, extMigrator, [profileDir]));
  389.         }
  390.  
  391.         // Check the profiles of the various other moz-apps for calendar data
  392.         var profiles = [];
  393.  
  394.         // Do they use Firefox?
  395.         var ffProf, sbProf, tbProf;
  396.         if ((ffProf = this.getFirefoxProfile())) {
  397.             profiles.push(ffProf);
  398.         }
  399.  
  400.         if (this.mIsLightning) {
  401.             // If we're lightning, check Sunbird
  402.             if ((sbProf = this.getSunbirdProfile())) {
  403.                 profiles.push(sbProf);
  404.             }
  405.         } else {
  406.             // Otherwise, check Thunderbird
  407.             if ((tbProf = this.getThunderbirdProfile())) {
  408.                 profiles.push(tbProf);
  409.             }
  410.         }
  411.  
  412.         // Now check all of the profiles in each of these folders for data
  413.         for each (var prof in profiles) {
  414.             var dirEnum = prof.directoryEntries;
  415.             while (dirEnum.hasMoreElements()) {
  416.                 var profile = dirEnum.getNext().QueryInterface(Components.interfaces.nsIFile);
  417.                 if (profile.isFile()) {
  418.                     continue;
  419.                 } else {
  420.                     profile.append("Calendar");
  421.                     if (profile.exists()) {
  422.                         LOG("Found old extension directory at" + profile.path);
  423.                         var title = "Mozilla Calendar";
  424.                         migrators.push(new dataMigrator(title, extMigrator, [profile]));
  425.                     }
  426.                 }
  427.             }
  428.         }
  429.  
  430.         return migrators;
  431.     },
  432.  
  433.     /**
  434.      * Checks to see if Apple's iCal is installed and offers to migrate any data
  435.      * the user has created in it.
  436.      */
  437.     checkIcal: function gdm_ical() {
  438.         LOG("Checking for ical data");
  439.  
  440.         function icalMigrate(aDataDir, aCallback) {
  441.             aDataDir.append("Sources");
  442.             var dirs = aDataDir.directoryEntries;
  443.             var calManager = getCalendarManager();
  444.  
  445.             var i = 1;
  446.             while(dirs.hasMoreElements()) {
  447.                 var dataDir = dirs.getNext().QueryInterface(Components.interfaces.nsIFile);
  448.                 var dataStore = dataDir.clone();
  449.                 dataStore.append("corestorage.ics");
  450.                 if (!dataStore.exists()) {
  451.                     continue;
  452.                 }
  453.  
  454.                 var chars = [];
  455.                 var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
  456.                                  .createInstance(Components.interfaces.nsIFileInputStream);
  457.  
  458.                 fileStream.init(dataStore, 0x01, 0444, {});
  459.                 var convStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
  460.                                  .getService(Components.interfaces.nsIConverterInputStream);
  461.                                  convStream.init(fileStream, 'UTF-8', 0, 0x0000);
  462.                 var tmpStr = {};
  463.                 var str = "";
  464.                 while (convStream.readString(-1, tmpStr)) {
  465.                     str += tmpStr.value;
  466.                 }
  467.  
  468.                 // Strip out the timezone definitions, since it makes the file
  469.                 // invalid otherwise
  470.                 var index = str.indexOf(";TZID=");
  471.                 while (index != -1) {
  472.                     var endIndex = str.indexOf(':', index);
  473.                     var otherEnd = str.indexOf(';', index+2);
  474.                     if (otherEnd < endIndex) {
  475.                         endIndex = otherEnd;
  476.                     }
  477.                     var sub = str.substring(index, endIndex);
  478.                     str = str.replace(sub, "", "g");
  479.                     index = str.indexOf(";TZID=");
  480.                 }
  481.                 var tempFile = gDataMigrator.dirService.get("TmpD", Components.interfaces.nsIFile);
  482.                 tempFile.append("icalTemp.ics");
  483.                 tempFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
  484.                 var tempUri = gDataMigrator.ioService.newFileURI(tempFile);
  485.  
  486.                 var stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
  487.                              .createInstance(Components.interfaces.nsIFileOutputStream);
  488.                 stream.init(tempFile, 0x2A, 0600, 0);
  489.                 var convStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
  490.                                 .getService(Components.interfaces.nsIConverterOutputStream);
  491.                 convStream.init(stream, 'UTF-8', 0, 0x0000);
  492.                 convStream.writeString(str);
  493.  
  494.                 var cal = gDataMigrator.importICSToStorage(tempFile);
  495.                 cal.name = "iCalendar"+i;
  496.                 i++;
  497.             }
  498.             LOG("icalMig making callback");
  499.             aCallback();
  500.         }
  501.         var profileDir = this.dirService.get("ProfD", Components.interfaces.nsILocalFile);
  502.         var icalSpec = profileDir.path;
  503.         var icalFile;
  504.         if (!this.isLightning()) {
  505.             var diverge = icalSpec.indexOf("Sunbird");
  506.             if (diverge == -1) {
  507.                 return [];
  508.             }
  509.             icalSpec = icalSpec.substr(0, diverge);
  510.             icalFile = Components.classes["@mozilla.org/file/local;1"]
  511.                        .createInstance(Components.interfaces.nsILocalFile);
  512.             icalFile.initWithPath(icalSpec);
  513.         } else {
  514.             var diverge = icalSpec.indexOf("Thunderbird");
  515.             if (diverge == -1) {
  516.                 return [];
  517.             }
  518.             icalSpec = icalSpec.substr(0, diverge);
  519.             icalFile = Components.classes["@mozilla.org/file/local;1"]
  520.                        .createInstance(Components.interfaces.nsILocalFile);
  521.             icalFile.initWithPath(icalSpec);
  522.             icalFile.append("Application Support");
  523.         }
  524.  
  525.         icalFile.append("iCal");
  526.         if (icalFile.exists()) {
  527.             return [new dataMigrator("Apple iCal", icalMigrate, [icalFile])];
  528.         }
  529.  
  530.         return [];
  531.     },
  532.  
  533.     /**
  534.      * Checks to see if Evolution is installed and offers to migrate any data
  535.      * stored there.
  536.      */
  537.     checkEvolution: function gdm_evolution() {
  538.         LOG("Checking for evolution data");
  539.  
  540.         function evoMigrate(aDataDir, aCallback) {
  541.             aDataDir.append("Sources");
  542.             var dirs = aDataDir.directoryEntries;
  543.             var calManager = getCalendarManager();
  544.  
  545.             var i = 1;
  546.             while(dirs.hasMoreElements()) {
  547.                 var dataDir = dirs.getNext().QueryInterface(Components.interfaces.nsIFile);
  548.                 var dataStore = dataDir.clone();
  549.                 dataStore.append("calendar.ics");
  550.                 if (!dataStore.exists()) {
  551.                     continue;
  552.                 }
  553.  
  554.                 var cal = gDataMigrator.importICSToStorage(dataStore);
  555.                 //XXX
  556.                 cal.name = "Evolution"+i;
  557.                 i++;
  558.             }
  559.             aCallback();
  560.         }
  561.  
  562.         var profileDir = this.dirService.get("ProfD", Components.interfaces.nsILocalFile);
  563.         var evoSpec = profileDir.path;
  564.         var evoFile;
  565.         if (!this.mIsLightning) {
  566.             var diverge = evoSpec.indexOf(".mozilla");
  567.             if (diverge == -1) {
  568.                 return [];
  569.             }
  570.             evoSpec = evoSpec.substr(0, diverge);
  571.             evoFile = Components.classes["@mozilla.org/file/local;1"]
  572.                        .createInstance(Components.interfaces.nsILocalFile);
  573.             evoFile.initWithPath(evoSpec);
  574.         } else {
  575.             var diverge = evoSpec.indexOf(".thunderbird");
  576.             if (diverge == -1) {
  577.                 return [];
  578.             }
  579.             evoSpec = evoSpec.substr(0, diverge);
  580.             evoFile = Components.classes["@mozilla.org/file/local;1"]
  581.                        .createInstance(Components.interfaces.nsILocalFile);
  582.             evoFile.initWithPath(evoSpec);
  583.         }
  584.         evoFile.append(".evolution");
  585.         evoFile.append("calendar");
  586.         evoFile.append("local");
  587.         evoFile.append("system");
  588.         if (evoFile.exists()) {
  589.             return [new dataMigrator("Evolution", evoMigrate, [evoFile])];
  590.         }
  591.         return [];
  592.     },
  593.  
  594.     importICSToStorage: function migrateIcsStorage(icsFile) {
  595.         var calManager = getCalendarManager();
  596.         var uris = [];
  597.         for each (var oldCal in calManager.getCalendars({})) {
  598.             uris.push(oldCal.uri.spec);
  599.         }
  600.         var uri = 'moz-profile-calendar://?id=';
  601.         var i = 1;
  602.         while (uris.indexOf(uri+i) != -1) {
  603.             i++;
  604.         }
  605.  
  606.         var cal = calManager.createCalendar("storage", makeURL(uri+i));
  607.         var icsImporter = Components.classes["@mozilla.org/calendar/import;1?type=ics"]
  608.                           .getService(Components.interfaces.calIImporter);
  609.  
  610.         var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
  611.                           .createInstance(Components.interfaces.nsIFileInputStream);
  612.         var items = [];
  613.  
  614.         try {
  615.             inputStream.init(icsFile, MODE_RDONLY, 0444, {});
  616.             items = icsImporter.importFromStream(inputStream, {});
  617.         } catch(ex) {
  618.             switch (ex.result) {
  619.                 case Components.interfaces.calIErrors.INVALID_TIMEZONE:
  620.                     showError(calGetString("calendar", "timezoneError", [icsFile.path] , 1));
  621.                     break;
  622.                 default:
  623.                     showError(calGetString("calendar", "unableToRead") + icsFile.path + "\n"+ ex);
  624.             }
  625.         } finally {
  626.            inputStream.close();
  627.         }
  628.  
  629.         // Defined in import-export.js
  630.         putItemsIntoCal(cal, items);
  631.  
  632.         calManager.registerCalendar(cal);
  633.         getCompositeCalendar().addCalendar(cal);
  634.         return cal;
  635.     },
  636.  
  637.     /**
  638.      * Helper functions for getting the profile directory of various MozApps
  639.      * (Getting the profile dir is way harder than it should be.)
  640.      *
  641.      * Sunbird:
  642.      *     Unix:     ~jdoe/.mozilla/sunbird/
  643.      *     Windows:  %APPDATA%\Mozilla\Sunbird\Profiles
  644.      *     Mac OS X: ~jdoe/Library/Application Support/Sunbird/Profiles
  645.      *
  646.      * Firefox:
  647.      *     Unix:     ~jdoe/.mozilla/firefox/
  648.      *     Windows:  %APPDATA%\Mozilla\Firefox\Profiles
  649.      *     Mac OS X: ~jdoe/Library/Application Support/Firefox/Profiles
  650.      *
  651.      * Thunderbird:
  652.      *     Unix:     ~jdoe/.thunderbird/
  653.      *     Windows:  %APPDATA%\Thunderbird\Profiles
  654.      *     Mac OS X: ~jdoe/Library/Thunderbird/Profiles
  655.      *
  656.      * Notice that Firefox and Sunbird follow essentially the same pattern, so
  657.      * we group them with getNormalProfile
  658.      */
  659.     getFirefoxProfile: function gdm_getFF() {
  660.         return this.getNormalProfile("Firefox");
  661.     },
  662.  
  663.     getThunderbirdProfile: function gdm_getTB() {
  664.         var localFile;
  665.         var profileRoot = this.dirService.get("DefProfRt", Components.interfaces.nsILocalFile);
  666.         LOG("profileRoot = " + profileRoot.path);
  667.         if (this.mIsLightning) {
  668.             localFile = profileRoot;
  669.         } else {
  670.             // Now it gets ugly
  671.             switch (this.mPlatform) {
  672.                 case "darwin": // Mac OS X
  673.                 case "winnt":
  674.                     localFile = profileRoot.parent.parent.parent;
  675.                     localFile.append("Thunderbird");
  676.                     localFile.append("Profiles");
  677.                     break;
  678.                 default: // Unix
  679.                     localFile = profileRoot.parent.parent;
  680.                     localFile.append(".thunderbird");
  681.             }
  682.         }
  683.         LOG("searching for Thunderbird in " + localFile.path);
  684.         return localFile.exists() ? localFile : null;
  685.     },
  686.  
  687.     getSunbirdProfile: function gdm_getSB() {
  688.         return this.getNormalProfile("Sunbird");
  689.     },
  690.  
  691.     getNormalProfile: function gdm_getNorm(aAppName) {
  692.         var localFile;
  693.         var profileRoot = this.dirService.get("DefProfRt", Components.interfaces.nsILocalFile);
  694.         LOG("profileRoot = " + profileRoot.path);
  695.  
  696.         if (this.isLightning()) {  // We're in Thunderbird
  697.             switch (this.mPlatform) {
  698.                 case "darwin": // Mac OS X
  699.                     localFile = profileRoot.parent.parent;
  700.                     localFile.append("Application Support");
  701.                     localFile.append(aAppName);
  702.                     localFile.append("Profiles");
  703.                     break;
  704.                 case "winnt":
  705.                     localFile = profileRoot.parent.parent;
  706.                     localFile.append("Mozilla");
  707.                     localFile.append(aAppName);
  708.                     localFile.append("Profiles");
  709.                     break;
  710.                 default: // Unix
  711.                     localFile = profileRoot.parent;
  712.                     localFile.append(".mozilla");
  713.                     localFile.append(aAppName.toLowerCase());
  714.                     break;
  715.             }
  716.         } else {
  717.             switch (this.mPlatform) {
  718.                 // On Mac and Windows, we can just remove the "Sunbird" and
  719.                 // replace it with "Firefox" to get to Firefox
  720.                 case "darwin": // Mac OS X
  721.                 case "winnt":
  722.                     localFile = profileRoot.parent.parent;
  723.                     localFile.append(aAppName);
  724.                     localFile.append("Profiles");
  725.                     break;
  726.                 default: // Unix
  727.                     localFile = profileRoot.parent;
  728.                     localFile.append(aAppName.toLowerCase());
  729.                     break;
  730.             }
  731.         }
  732.         LOG("searching for " + aAppName + " in " + localFile.path);
  733.         return localFile.exists() ? localFile : null;
  734.     }
  735. };
  736.  
  737. function LOG(aString) {
  738.     if (!getPrefSafe("calendar.migration.log", false)) {
  739.         return;
  740.     }
  741.     var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
  742.                          .getService(Components.interfaces.nsIConsoleService);
  743.     consoleService.logStringMessage(aString);
  744.     dump(aString+"\n");
  745. }
  746.